package org.amse.ys.sndeditor.model.soundfile;

import java.io.*;
import java.lang.ref.SoftReference;

import org.amse.ys.sndeditor.model.ISoundFile;

public class WavReader implements ISoundFile {

	// our private variables
	// private String myPath;
	private long myChunkSize;
	private long mySubChunk1Size;
	private int myFormat;
	private long myNumberOfChannels;
	private long mySampleRate;
	private long myByteRate;
	private int myBlockAlign;
	private int myBitsPerSample;
	private long myDataSize;
	private boolean myFileIsOpened = false;
	private String myFileName;

	private byte[] myData;
	private byte[] myBuffer = null;
	private SoftReference<byte[]> myDataSoftReference;
	private boolean myStateIsSaved;

	public WavReader() {
	}

	private static String fileNameHash(String openedFile) {
		char[] c = new char[openedFile.length()];
		for (int i = 0; i < openedFile.length(); i++) {
			c[i] = openedFile.charAt(i);
			if (c[i] == '/') {
				c[i] = '_';
			}
		}
		return new String(c);

	}

	// read a wav file into this class
	public void openFromFile(String fileName) throws IOException {
		myStateIsSaved = false;

		myFileName = fileName;
		DataInputStream inFile = null;
		myData = null;
		byte[] tmpLong = new byte[4];
		byte[] tmpInt = new byte[2];

	
		inFile = new DataInputStream(new FileInputStream(fileName));
		
		String chunkID = "" + (char) inFile.readByte()
				+ (char) inFile.readByte() + (char) inFile.readByte()
				+ (char) inFile.readByte();
		inFile.read(tmpLong); // read the ChunkSize
		myChunkSize = byteArrayToLong(tmpLong);
		String format = "" + (char) inFile.readByte()
				+ (char) inFile.readByte() + (char) inFile.readByte()
				+ (char) inFile.readByte();
		String subChunk1ID = "" + (char) inFile.readByte()
				+ (char) inFile.readByte() + (char) inFile.readByte()
				+ (char) inFile.readByte();
		inFile.read(tmpLong); // read the SubChunk1Size
		mySubChunk1Size = byteArrayToLong(tmpLong);
		inFile.read(tmpInt); // read the audio format. This should be 1 for PCM
		myFormat = byteArrayToInt(tmpInt);
		inFile.read(tmpInt); // read the # of channels (1 or 2)
		myNumberOfChannels = byteArrayToInt(tmpInt);
		inFile.read(tmpLong); // read the samplerate
		mySampleRate = byteArrayToLong(tmpLong);
		inFile.read(tmpLong); // read the byterate
		myByteRate = byteArrayToLong(tmpLong);
		inFile.read(tmpInt); // read the blockalign
		myBlockAlign = byteArrayToInt(tmpInt);
		inFile.read(tmpInt); // read the bitspersample
		myBitsPerSample = byteArrayToInt(tmpInt);

		// read the data chunk header - reading this IS necessary, because not
		// all wav files will have the data chunk here - for now, we're just
		// assuming that the data chunk is here
		String dataChunkID = "" + (char) inFile.readByte()
				+ (char) inFile.readByte() + (char) inFile.readByte()
				+ (char) inFile.readByte();

		inFile.read(tmpLong); // read the size of the data
		myDataSize = byteArrayToLong(tmpLong);

		// read the data chunk
		myData = new byte[(int) myDataSize];
		inFile.read(myData);

		// close the input stream
		inFile.close();
		myFileIsOpened = true;
	}

	// write out the wav file
	public void saveToFile(String fileName) throws IOException {
		DataOutputStream outFile = new DataOutputStream(new FileOutputStream(
				fileName));

		// write the wav file per the wav file format
		outFile.writeBytes("RIFF"); // 00 - RIFF
		outFile.write(intToByteArray((int) myChunkSize), 0, 4); // 04 - how big
																// is the rest
																// of this file?
		outFile.writeBytes("WAVE"); // 08 - WAVE
		outFile.writeBytes("fmt "); // 12 - fmt
		outFile.write(intToByteArray((int) mySubChunk1Size), 0, 4); // 16 - size
																	// of this
																	// chunk
		outFile.write(shortToByteArray((short) myFormat), 0, 2); // 20 - what is
																	// the audio
																	// format? 1
																	// for PCM =
																	// Pulse
																	// Code
																	// Modulation
		outFile.write(shortToByteArray((short) myNumberOfChannels), 0, 2); // 22
																			// -
																			// mono
																			// or
																			// stereo?
																			// 1
																			// or
																			// 2?
																			// (or
																			// 5
																			// or
																			// ???)
		outFile.write(intToByteArray((int) mySampleRate), 0, 4); // 24 - samples
																	// per
																	// second
																	// (numbers
																	// per
																	// second)
		outFile.write(intToByteArray((int) myByteRate), 0, 4); // 28 - bytes per
																// second
		outFile.write(shortToByteArray((short) myBlockAlign), 0, 2); // 32 - #
																		// of
																		// bytes
																		// in
																		// one
																		// sample,
																		// for
																		// all
																		// channels
		outFile.write(shortToByteArray((short) myBitsPerSample), 0, 2); // 34 -
																		// how
																		// many
																		// bits
																		// in a
																		// sample(number)?
																		// usually
																		// 16 or
																		// 24
		outFile.writeBytes("data"); // 36 - data
		outFile.write(intToByteArray((int) myDataSize), 0, 4); // 40 - how big
																// is this data
																// chunk
		outFile.write(myData); // 44 - the actual data itself - just a long
								// string of numbers
		outFile.close();		
		myFileName = fileName;
	}

	// ===========================
	// CONVERT BYTES TO JAVA TYPES
	// ===========================

	// these two routines convert a byte array to a unsigned short
	public static int byteArrayToInt(byte[] b) {
		int start = 0;
		int low = b[start] & 0xff;
		int high = b[start + 1] & 0xff;
		return (int) (high << 8 | low);
	}

	// these two routines convert a byte array to an unsigned integer
	public static long byteArrayToLong(byte[] b) {
		int start = 0;
		int i = 0;
		int len = 4;
		int cnt = 0;
		byte[] tmp = new byte[len];
		for (i = start; i < (start + len); i++) {
			tmp[cnt] = b[i];
			cnt++;
		}
		long accum = 0;
		i = 0;
		for (int shiftBy = 0; shiftBy < 32; shiftBy += 8) {
			accum |= ((long) (tmp[i] & 0xff)) << shiftBy;
			i++;
		}
		return accum;
	}

	// CONVERT JAVA TYPES TO BYTES
	// returns a byte array of length 4
	private static byte[] intToByteArray(int i) {
		byte[] b = new byte[4];
		b[0] = (byte) (i & 0x00FF);
		b[1] = (byte) ((i >> 8) & 0x000000FF);
		b[2] = (byte) ((i >> 16) & 0x000000FF);
		b[3] = (byte) ((i >> 24) & 0x000000FF);
		return b;
	}

	// convert a short to a byte array
	public static byte[] shortToByteArray(short data) {
		return new byte[] { (byte) (data & 0xff), (byte) ((data >>> 8) & 0xff) };
	}

	public int giveDepthInBits() {
		return myBitsPerSample;
	}

	public int giveIntegerIntensityAtPoint(int point, int chanel) {
		int result = 0;
		int bytesPerSample = myBitsPerSample >>> 3;
		int dataShift = bytesPerSample
				* (point * (int) myNumberOfChannels + chanel);
		int filter = 1;
		for (int i = 0; i < bytesPerSample; i++) {
			result = result + myData[dataShift] * filter;
			dataShift++;
			filter = (filter << 8);
		}
		return result;
	}

	public int giveLengthInPoints() {
		int result = (int) ((myDataSize) / ((int) myNumberOfChannels
				* myBitsPerSample >>> 3));
		return result;
	}

	public int giveBufferLengthInPoints() {
		int result = (int) ((myBuffer.length) / ((int) myNumberOfChannels
				* myBitsPerSample >>> 3));
		return result;
	}

	public int giveNumberOfChannels() {
		return (int) myNumberOfChannels;
	}

	public int givePointsPerSecond() {
		return (int) mySampleRate;
	}

	public boolean isOpened() {
		return myFileIsOpened;
	}

	public boolean copyToClipBoard(int startPoint, int endPoint) {
		int lengthInPoints = Math.abs(startPoint - endPoint);
		int start = Math.min(startPoint, endPoint);
		int bytesPerStep = giveNumberOfChannels() * (giveDepthInBits() / 8);
		myBuffer = new byte[lengthInPoints * bytesPerStep];

		System.arraycopy(myData, start * bytesPerStep, myBuffer, 0,
				lengthInPoints * bytesPerStep);
		return true;
	}

	public boolean pasteFromClipBoard(int position) {
		if (myBuffer == null) {
			return false;
		}
		// position--;
		byte[] tmp = myData;

		myData = new byte[myData.length + myBuffer.length];
		int bytesPerStep = giveNumberOfChannels() * (giveDepthInBits() / 8);
		int divider = position * bytesPerStep;

		System.arraycopy(tmp, 0, myData, 0, divider);
		System.arraycopy(myBuffer, 0, myData, divider, myBuffer.length);
		System.arraycopy(tmp, divider, myData, divider + myBuffer.length,
				tmp.length - divider);

		myDataSize = myData.length;
		return true;
	}

	public int toPointNumber(double time) {
		return (int) (time * givePointsPerSecond() / giveNumberOfChannels());
	}

	public double toSeconds(int pointNumber) {
		return ((double) pointNumber * giveNumberOfChannels() / (givePointsPerSecond()));
	}

	public boolean cut(int startPoint, int endPoint) {
		int start = Math.min(startPoint, endPoint);
		int end = Math.max(startPoint, endPoint);
		int bytesPerStep = giveNumberOfChannels() * (giveDepthInBits() / 8);
		int point1 = start * bytesPerStep;
		int point2 = end * bytesPerStep;

		byte[] tmp = myData;
		myData = new byte[myData.length - (point2 - point1)];
		System.arraycopy(tmp, 0, myData, 0, point1);
		System.arraycopy(tmp, point2, myData, point1, myData.length - point1);

		myDataSize = myData.length;
		return true;

	}

	private static String giveTempoaryFileName(String originalFileName) {
		return ("/sdcard/soundeditor/" + fileNameHash(originalFileName) + ".tmp");
	}

	@Override
	public void openTempoaryInformation() throws IOException {
		
		myStateIsSaved = false;
		myData = myDataSoftReference.get();
		if (myData == null) {
			
			String tmp = myFileName;
			openFromFile(giveTempoaryFileName(myFileName));
			myFileName = tmp;
		}
	}

	@Override
	public void saveTempoaryInformation() throws IOException {
		if (this.myData == null) {
			return;
		}
		String tmp = myFileName;
		myDataSoftReference = new SoftReference<byte[]>(myData);
		File f = new File("/sdcard/soundeditor/");
		if (!f.exists()) {
			if (f.mkdir()) {
				System.out.println("directory created");
			} else {
				System.out.println("fail to create directory");
			}
		}
		

		saveToFile(giveTempoaryFileName(myFileName));
		myData = null;
		myStateIsSaved = true;
		myFileName = tmp;
	}

	@Override
	public boolean closeFile() {
		myStateIsSaved = false;
		if (hasTempoaryInformation()) {
			File f = new File(giveTempoaryFileName(myFileName));
			return f.delete();
		} else {
			return true;
		}
	}

	@Override
	public boolean hasTempoaryInformation() {
		return (new File(giveTempoaryFileName(myFileName))).exists();
	}

	@Override
	public boolean hasTempoaryInformation(String file) {
		return (new File(giveTempoaryFileName(file))).exists();
	}

	public static boolean HasTempoaryInformation(String file) {
		return (new File(giveTempoaryFileName(file))).exists();
	}

	@Override
	public boolean stateIsSaved() {
		return myStateIsSaved;
	}

	@Override
	public String getFileName() {
		return myFileName;
	}

}